import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
import AvatarNode
import TelegramStringFormatting
import AccountContext
import ChatListSearchItemHeader
import AnimatedAvatarSetNode

private func callDurationString(strings: PresentationStrings, duration: Int32) -> String {
    if duration < 60 {
        return strings.Call_ShortSeconds(duration)
    } else {
        return strings.Call_ShortMinutes(duration / 60)
    }
}

private func callListNeighbors(item: ListViewItem, topItem: ListViewItem?, bottomItem: ListViewItem?) -> ItemListNeighbors {
    let topNeighbor: ItemListNeighbor
    if let topItem = topItem {
        if let item = item as? ItemListItem, let topItem = topItem as? ItemListItem {
            if topItem.sectionId != item.sectionId {
                topNeighbor = .otherSection(topItem.requestsNoInset ? .none : .full)
            } else {
                topNeighbor = .sameSection(alwaysPlain: topItem.isAlwaysPlain)
            }
        } else {
            if item is CallListCallItem && topItem is CallListCallItem {
                topNeighbor = .sameSection(alwaysPlain: false)
            } else {
                topNeighbor = .otherSection(.full)
            }
        }
    } else {
        topNeighbor = .none
    }
    
    let bottomNeighbor: ItemListNeighbor
    if let bottomItem = bottomItem {
        if let item = item as? ItemListItem, let bottomItem = bottomItem as? ItemListItem {
            if bottomItem.sectionId != item.sectionId {
                bottomNeighbor = .otherSection(bottomItem.requestsNoInset ? .none : .full)
            } else {
                bottomNeighbor = .sameSection(alwaysPlain: bottomItem.isAlwaysPlain)
            }
        } else {
            if item is CallListCallItem && bottomItem is CallListCallItem {
                bottomNeighbor = .sameSection(alwaysPlain: false)
            } else {
                bottomNeighbor = .otherSection(.full)
            }
        }
    } else {
        bottomNeighbor = .none
    }
    
    return ItemListNeighbors(top: topNeighbor, bottom: bottomNeighbor)
}

class CallListCallItem: ListViewItem {
    let presentationData: ItemListPresentationData
    let dateTimeFormat: PresentationDateTimeFormat
    let context: AccountContext
    let style: ItemListStyle
    let topMessage: EngineMessage
    let messages: [EngineMessage]
    let editing: Bool
    let revealed: Bool
    let interaction: CallListNodeInteraction
    
    let selectable: Bool = true
    let headerAccessoryItem: ListViewAccessoryItem?
    let header: ListViewItemHeader?
    
    init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, style: ItemListStyle, topMessage: EngineMessage, messages: [EngineMessage], editing: Bool, revealed: Bool, displayHeader: Bool, interaction: CallListNodeInteraction) {
        self.presentationData = presentationData
        self.dateTimeFormat = dateTimeFormat
        self.context = context
        self.style = style
        self.topMessage = topMessage
        self.messages = messages
        self.editing = editing
        self.revealed = revealed
        self.interaction = interaction
        
        self.headerAccessoryItem = nil
        if displayHeader {
            self.header = ChatListSearchItemHeader(type: .recentCalls, theme: presentationData.theme, strings: presentationData.strings)
        } else {
            self.header = nil
        }
    }
    
    func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
        async {
            let node = CallListCallItemNode()
            let makeLayout = node.asyncLayout()
            let (first, last, firstWithHeader) = CallListCallItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
            let (nodeLayout, nodeApply) = makeLayout(self, params, first, last, firstWithHeader, callListNeighbors(item: self, topItem: previousItem, bottomItem: nextItem))
            node.contentSize = nodeLayout.contentSize
            node.insets = nodeLayout.insets
            
            Queue.mainQueue().async {
                completion(node, {
                    return (nil, { _ in
                        nodeApply(synchronousLoads).1(false)
                    })
                })
            }
        }
    }
    
    func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
        Queue.mainQueue().async {
            if let nodeValue = node() as? CallListCallItemNode {
                let layout = nodeValue.asyncLayout()
                async {
                    let (first, last, firstWithHeader) = CallListCallItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
                    let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, callListNeighbors(item: self, topItem: previousItem, bottomItem: nextItem))
                    var animated = true
                    if case .None = animation {
                        animated = false
                    }
                    Queue.mainQueue().async {
                        completion(nodeLayout, { _ in
                            apply(false).1(animated)
                        })
                    }
                }
            }
        }
    }
    
    func selected(listView: ListView) {
        listView.clearHighlightAnimated(true)
        self.interaction.call(self.topMessage)
    }
    
    static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
        var first = false
        var last = false
        var firstWithHeader = false
        if let previousItem = previousItem {
            if let header = item.header {
                if let previousItem = previousItem as? CallListCallItem {
                    firstWithHeader = header.id != previousItem.header?.id
                } else {
                    firstWithHeader = true
                }
            }
        } else {
            first = true
            firstWithHeader = item.header != nil
        }
        if let nextItem = nextItem {
            if let header = item.header {
                if let nextItem = nextItem as? CallListCallItem {
                    last = header.id != nextItem.header?.id
                } else {
                    last = true
                }
            }
        } else {
            last = true
        }
        return (first, last, firstWithHeader)
    }
}

private let avatarFont = avatarPlaceholderFont(size: 16.0)
private let multipleAvatarFont = avatarPlaceholderFont(size: 12.0)

class CallListCallItemNode: ItemListRevealOptionsItemNode {
    private let backgroundNode: ASDisplayNode
    private let topStripeNode: ASDisplayNode
    private let bottomStripeNode: ASDisplayNode
    private let highlightedBackgroundNode: ASDisplayNode
    private let maskNode: ASImageNode
    
    private let containerNode: ASDisplayNode
    override var controlsContainer: ASDisplayNode {
        return self.containerNode
    }
    
    private let avatarNode: AvatarNode
    
    private var conferenceAvatarListContext: AnimatedAvatarSetContext?
    private var conferenceAvatarListNode: AnimatedAvatarSetNode?

    private let titleNode: TextNode
    private var credibilityIconNode: ASImageNode?
    private let statusNode: TextNode
    private let dateNode: TextNode
    private let typeIconNode: ASImageNode
    private let infoButtonNode: HighlightableButtonNode
    
    var editableControlNode: ItemListEditableControlNode?
    
    private let accessibilityArea: AccessibilityAreaNode
    
    private var layoutParams: (CallListCallItem, ListViewItemLayoutParams, Bool, Bool, Bool)?
    
    required init() {
        self.backgroundNode = ASDisplayNode()
        self.backgroundNode.isLayerBacked = true
        
        self.containerNode = ASDisplayNode()
        
        self.maskNode = ASImageNode()
        self.maskNode.isUserInteractionEnabled = false
        
        self.topStripeNode = ASDisplayNode()
        self.topStripeNode.isLayerBacked = true
        
        self.bottomStripeNode = ASDisplayNode()
        self.bottomStripeNode.isLayerBacked = true
        
        self.highlightedBackgroundNode = ASDisplayNode()
        self.highlightedBackgroundNode.isLayerBacked = true
        
        self.avatarNode = AvatarNode(font: avatarFont)
        self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
        
        self.titleNode = TextNode()
        self.statusNode = TextNode()
        self.dateNode = TextNode()
        
        self.typeIconNode = ASImageNode()
        self.typeIconNode.isLayerBacked = true
        self.typeIconNode.displayWithoutProcessing = true
        self.typeIconNode.displaysAsynchronously = false
        
        self.infoButtonNode = HighlightableButtonNode()
        self.infoButtonNode.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -10.0)
        
        self.accessibilityArea = AccessibilityAreaNode()
        
        super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
        
        self.addSubnode(self.backgroundNode)
        self.addSubnode(self.containerNode)
        self.containerNode.addSubnode(self.avatarNode)
        self.containerNode.addSubnode(self.typeIconNode)
        self.containerNode.addSubnode(self.titleNode)
        self.containerNode.addSubnode(self.statusNode)
        self.containerNode.addSubnode(self.dateNode)
        self.containerNode.addSubnode(self.infoButtonNode)
        self.addSubnode(self.accessibilityArea)
        
        self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
        
        self.accessibilityArea.activate = { [weak self] in
            guard let item = self?.layoutParams?.0 else {
                return false
            }
            item.interaction.call(item.topMessage)
            return true
        }
    }
    
    override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
        if let (item, _, _, _, _) = self.layoutParams {
            let (first, last, firstWithHeader) = CallListCallItem.mergeType(item: item, previousItem: previousItem, nextItem: nextItem)
            self.layoutParams = (item, params, first, last, firstWithHeader)
            let makeLayout = self.asyncLayout()
            let (nodeLayout, nodeApply) = makeLayout(item, params, first, last, firstWithHeader, callListNeighbors(item: item, topItem: previousItem, bottomItem: nextItem))
            self.contentSize = nodeLayout.contentSize
            self.insets = nodeLayout.insets
            let _ = nodeApply(false)
        }
    }
    
    override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
        super.setHighlighted(highlighted, at: point, animated: animated)
        
        if highlighted {
            self.highlightedBackgroundNode.alpha = 1.0
            if self.highlightedBackgroundNode.supernode == nil {
                if self.backgroundNode.supernode != nil {
                    self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.backgroundNode)
                } else {
                    self.insertSubnode(self.highlightedBackgroundNode, at: 0)
                }
            }
        } else {
            if self.highlightedBackgroundNode.supernode != nil {
                if animated {
                    self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
                        if let strongSelf = self {
                            if completed {
                                strongSelf.highlightedBackgroundNode.removeFromSupernode()
                            }
                        }
                    })
                    self.highlightedBackgroundNode.alpha = 0.0
                } else {
                    self.highlightedBackgroundNode.removeFromSupernode()
                }
            }
        }
    }
    
    func asyncLayout() -> (_ item: CallListCallItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> (Signal<Void, NoError>?, (Bool) -> Void)) {
        let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
        let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
        let makeDateLayout = TextNode.asyncLayout(self.dateNode)
        let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
        let currentItem = self.layoutParams?.0
        
        return { [weak self] item, params, first, last, firstWithHeader, neighbors in
            var updatedTheme: PresentationTheme?
            var updatedInfoIcon = false
            
            if currentItem?.presentationData.theme !== item.presentationData.theme {
                updatedTheme = item.presentationData.theme
                
                updatedInfoIcon = true
            }
            
            let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
            let statusFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
            let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
            let avatarDiameter = min(40.0, floor(item.presentationData.fontSize.itemListBaseFontSize * 40.0 / 17.0))
            let multipleAvatarDiameter = min(30.0, floor(item.presentationData.fontSize.itemListBaseFontSize * 30.0 / 17.0))
            
            let editingOffset: CGFloat
            var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
            if item.editing {
                let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
                editableControlSizeAndApply = sizeAndApply
                editingOffset = sizeAndApply.0
            } else {
                editingOffset = 0.0
            }
            
            var leftInset: CGFloat = 46.0 + avatarDiameter + params.leftInset
            let rightInset: CGFloat = 13.0 + params.rightInset
            var infoIconRightInset: CGFloat = rightInset - 1.0
            
            let insets: UIEdgeInsets
            let separatorHeight = UIScreenPixel
            let itemBackgroundColor: UIColor
            let itemSeparatorColor: UIColor
            
            switch item.style {
                case .plain:
                    itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
                    itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
                    insets = itemListNeighborsPlainInsets(neighbors)
                case .blocks:
                    itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
                    itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
                    insets = itemListNeighborsGroupedInsets(neighbors, params)
            }
            
            var dateRightInset: CGFloat = 46.0 + params.rightInset
            if item.editing {
                leftInset += editingOffset
                dateRightInset += 5.0
                infoIconRightInset -= 36.0
            }
            
            var titleAttributedString: NSAttributedString?
            var statusAttributedString: NSAttributedString?
            
            var statusAccessibilityString = ""
            
            var titleColor = item.presentationData.theme.list.itemPrimaryTextColor
            var hasMissed = false
            var hasIncoming = false
            var hasOutgoing = false
            var isVideo = false
            
            var hadDuration = false
            var callDuration: Int32?
            
            var isConference = false
            var conferenceIsDeclined = false
            
            let _ = isConference
            let _ = conferenceIsDeclined

            var conferenceAvatars: [EnginePeer] = []
            
            for message in item.messages {
                inner: for media in message.media {
                    if let action = media as? TelegramMediaAction {
                        if case let .phoneCall(_, discardReason, duration, video) = action.action {
                            isVideo = video
                            if message.flags.contains(.Incoming) {
                                hasIncoming = true
                                
                                if let discardReason = discardReason, case .missed = discardReason {
                                    titleColor = item.presentationData.theme.list.itemDestructiveColor
                                    hasMissed = true
                                }
                            } else {
                                hasOutgoing = true
                            }
                            if callDuration == nil && !hadDuration {
                                hadDuration = true
                                callDuration = duration
                            } else {
                                callDuration = nil
                            }
                        } else if case let .conferenceCall(conferenceCall) = action.action {
                            isConference = true

                            if let peer = message.peers[message.id.peerId], !conferenceAvatars.contains(where: { $0.id == peer.id }) {
                                conferenceAvatars.append(EnginePeer(peer))
                            }

                            for id in conferenceCall.otherParticipants {
                                if let peer = message.peers[id], !conferenceAvatars.contains(where: { $0.id == peer.id }) {
                                    conferenceAvatars.append(EnginePeer(peer))
                                }
                            }
                            
                            isVideo = conferenceCall.flags.contains(.isVideo)
                            if message.flags.contains(.Incoming) {
                                hasIncoming = true
                                let missedTimeout: Int32
                                #if DEBUG && false
                                missedTimeout = 5
                                #else
                                missedTimeout = 30
                                #endif
                                let currentTime = Int32(Date().timeIntervalSince1970)
                                if conferenceCall.flags.contains(.isMissed) {
                                    titleColor = item.presentationData.theme.list.itemDestructiveColor
                                    conferenceIsDeclined = true
                                } else if message.timestamp < currentTime - missedTimeout {
                                    titleColor = item.presentationData.theme.list.itemDestructiveColor
                                    hasMissed = true
                                }
                            } else {
                                hasOutgoing = true
                            }
                            if callDuration == nil && !hadDuration {
                                hadDuration = true
                                callDuration = conferenceCall.duration
                            } else {
                                callDuration = nil
                            }
                        }
                        break inner
                    }
                }
            }
            
            if let peer = item.topMessage.peers[item.topMessage.id.peerId] {
                if conferenceAvatars.count > 1 {
                    var peersString = ""
                    for peer in conferenceAvatars {
                        if !peersString.isEmpty {
                            peersString.append(", ")
                        }
                        if peer.id == item.context.account.peerId {
                            peersString += item.presentationData.strings.DialogList_You
                        } else {
                            peersString += peer.compactDisplayTitle
                        }
                    }
                    titleAttributedString = NSAttributedString(string: peersString, font: titleFont, textColor: titleColor)
                } else if let user = peer as? TelegramUser {
                    if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
                        let string = NSMutableAttributedString()
                        string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
                        string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
                        string.append(NSAttributedString(string: lastName, font: titleFont, textColor: titleColor))
                        if item.messages.count > 1 {
                            string.append(NSAttributedString(string: " (\(item.messages.count))", font: titleFont, textColor: titleColor))
                        }
                        titleAttributedString = string
                    } else if let firstName = user.firstName, !firstName.isEmpty {
                        titleAttributedString = NSAttributedString(string: firstName, font: titleFont, textColor: titleColor)
                    } else if let lastName = user.lastName, !lastName.isEmpty {
                        titleAttributedString = NSAttributedString(string: lastName, font: titleFont, textColor: titleColor)
                    } else {
                        titleAttributedString = NSAttributedString(string: item.presentationData.strings.User_DeletedAccount, font: titleFont, textColor: titleColor)
                    }
                } else if let group = peer as? TelegramGroup {
                    titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor)
                } else if let channel = peer as? TelegramChannel {
                    titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
                }
                
                if hasMissed {
                    statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallMissedShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
                    statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallMissed : item.presentationData.strings.Call_VoiceOver_VoiceCallMissed
                } else if hasIncoming && hasOutgoing {
                    statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallOutgoingShort + ", " + item.presentationData.strings.Notification_CallIncomingShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
                    statusAccessibilityString = isVideo ? (item.presentationData.strings.Call_VoiceOver_VideoCallOutgoing + ", " + item.presentationData.strings.Call_VoiceOver_VideoCallIncoming) : (item.presentationData.strings.Call_VoiceOver_VoiceCallOutgoing + ", " + item.presentationData.strings.Call_VoiceOver_VoiceCallIncoming)
                } else if hasIncoming {
                    if let callDuration = callDuration, callDuration != 0 {
                        statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallTimeFormat(item.presentationData.strings.Notification_CallIncomingShort, callDurationString(strings: item.presentationData.strings, duration: callDuration)).string, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
                        statusAccessibilityString = item.presentationData.strings.Notification_CallTimeFormat(isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallIncoming : item.presentationData.strings.Call_VoiceOver_VoiceCallIncoming, callDurationString(strings: item.presentationData.strings, duration: callDuration)).string
                        
                    } else {
                        statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallIncomingShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
                        statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallIncoming : item.presentationData.strings.Call_VoiceOver_VoiceCallIncoming
                    }
                } else {
                    if let callDuration = callDuration, callDuration != 0 {
                        statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallTimeFormat(item.presentationData.strings.Notification_CallOutgoingShort, callDurationString(strings: item.presentationData.strings, duration: callDuration)).string, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
                        statusAccessibilityString = item.presentationData.strings.Notification_CallTimeFormat(isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallOutgoing : item.presentationData.strings.Call_VoiceOver_VoiceCallOutgoing, callDurationString(strings: item.presentationData.strings, duration: callDuration)).string
                    } else {
                        statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallOutgoingShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
                        statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallOutgoing : item.presentationData.strings.Call_VoiceOver_VoiceCallOutgoing
                    }
                }
            }
            
            var t = Int(item.topMessage.timestamp)
            var timeinfo = tm()
            localtime_r(&t, &timeinfo)
            
            let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
            let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.topMessage.timestamp, relativeTo: timestamp, dateTimeFormat: item.dateTimeFormat)
            
            let (dateLayout, dateApply) = makeDateLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
            
            let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
            var currentCredibilityIconImage: UIImage?
            if let peer = item.topMessage.peers[item.topMessage.id.peerId], peer.id != item.context.account.peerId {
                if peer.isScam {
                    currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
                } else if peer.isFake {
                    currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
                } else if peer.isVerified {
                    currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
                } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled {
                    currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
                }
            }
            
            var additionalTitleInset: CGFloat = 0.0
            if let currentCredibilityIconImage = currentCredibilityIconImage {
                additionalTitleInset += 3.0 + currentCredibilityIconImage.size.width
            }
            
            let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - additionalTitleInset - leftInset - dateRightInset - dateLayout.size.width - (item.editing ? -30.0 : 10.0)), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
            
            let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - dateRightInset - dateLayout.size.width - (item.editing ? -30.0 : 10.0)), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
            
            let titleSpacing: CGFloat = -1.0
            let verticalInset: CGFloat = 6.0
            
            let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: titleLayout.size.height + titleSpacing + statusLayout.size.height + verticalInset * 2.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0))
            
            let outgoingVoiceIcon = PresentationResourcesCallList.outgoingIcon(item.presentationData.theme)
            let outgoingVideoIcon = PresentationResourcesCallList.outgoingVideoIcon(item.presentationData.theme)
            let infoIcon = PresentationResourcesCallList.infoButton(item.presentationData.theme)
            
            let outgoingIcon = isVideo ? outgoingVideoIcon : outgoingVoiceIcon
            
            let contentSize = nodeLayout.contentSize
            
            return (nodeLayout, { [weak self] synchronousLoads in
                if let strongSelf = self {
                    if let peer = item.topMessage.peers[item.topMessage.id.peerId] {
                        var overrideImage: AvatarNodeImageOverride?
                        if peer.isDeleted {
                            overrideImage = .deletedIcon
                        }
                        strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(peer), overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads)
                    }
                    
                    return (strongSelf.avatarNode.ready, { [weak strongSelf] animated in
                        if let strongSelf = strongSelf {
                            strongSelf.layoutParams = (item, params, first, last, firstWithHeader)
                            
                            let revealOffset = strongSelf.revealOffset
                            
                            let transition: ContainedViewLayoutTransition
                            if animated {
                                transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
                            } else {
                                transition = .immediate
                            }
                            
                            if let _ = updatedTheme {
                                strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
                                strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
                                strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
                                strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
                            }
                            
                            if let editableControlSizeAndApply = editableControlSizeAndApply {
                                let editableControlFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset, y: 0.0), size: CGSize(width: editableControlSizeAndApply.0, height: nodeLayout.contentSize.height))
                                if strongSelf.editableControlNode == nil {
                                    let editableControlNode = editableControlSizeAndApply.1(nodeLayout.contentSize.height)
                                    editableControlNode.tapped = {
                                        if let strongSelf = self {
                                            strongSelf.setRevealOptionsOpened(true, animated: true)
                                            strongSelf.revealOptionsInteractivelyOpened()
                                        }
                                    }
                                    strongSelf.editableControlNode = editableControlNode
                                    strongSelf.addSubnode(editableControlNode)
                                    editableControlNode.frame = editableControlFrame
                                    transition.animatePosition(node: editableControlNode, from: CGPoint(x: -editableControlFrame.size.width / 2.0, y: editableControlFrame.midY))
                                    editableControlNode.alpha = 0.0
                                    transition.updateAlpha(node: editableControlNode, alpha: 1.0)
                                } else {
                                    strongSelf.editableControlNode?.frame = editableControlFrame
                                }
                            } else if let editableControlNode = strongSelf.editableControlNode {
                                var editableControlFrame = editableControlNode.frame
                                editableControlFrame.origin.x = -editableControlFrame.size.width
                                strongSelf.editableControlNode = nil
                                transition.updateAlpha(node: editableControlNode, alpha: 0.0)
                                transition.updateFrame(node: editableControlNode, frame: editableControlFrame, completion: { [weak editableControlNode] _ in
                                    editableControlNode?.removeFromSupernode()
                                })
                            }
                            
                            switch item.style {
                                case .plain:
                                    if strongSelf.backgroundNode.supernode == nil {
                                        strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
                                    }
                                    if strongSelf.topStripeNode.supernode != nil {
                                        strongSelf.topStripeNode.removeFromSupernode()
                                    }
                                    if !last && strongSelf.bottomStripeNode.supernode == nil {
                                        strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1)
                                    } else if last && strongSelf.bottomStripeNode.supernode != nil {
                                        strongSelf.bottomStripeNode.removeFromSupernode()
                                    }
                                    if strongSelf.maskNode.supernode != nil {
                                        strongSelf.maskNode.removeFromSupernode()
                                    }
                                    transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)))
                                case .blocks:
                                    if strongSelf.backgroundNode.supernode == nil {
                                        strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
                                    }
                                    if strongSelf.topStripeNode.supernode == nil {
                                        strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
                                    }
                                    if strongSelf.bottomStripeNode.supernode == nil {
                                        strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
                                    }
                                    if strongSelf.maskNode.supernode == nil {
                                        strongSelf.addSubnode(strongSelf.maskNode)
                                    }
                                    let hasCorners = itemListHasRoundedBlockLayout(params)
                                    var hasTopCorners = false
                                    var hasBottomCorners = false
                                    switch neighbors.top {
                                        case .sameSection(false):
                                            strongSelf.topStripeNode.isHidden = true
                                        default:
                                            hasTopCorners = true
                                            strongSelf.topStripeNode.isHidden = hasCorners
                                    }
                                    let bottomStripeInset: CGFloat
                                    switch neighbors.bottom {
                                        case .sameSection(false):
                                            bottomStripeInset = leftInset
                                            strongSelf.bottomStripeNode.isHidden = false
                                        default:
                                            bottomStripeInset = 0.0
                                            hasBottomCorners = true
                                            strongSelf.bottomStripeNode.isHidden = hasCorners
                                    }
                                
                                    strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
                                    
                                    strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
                                    strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
                                    strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
                                    transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight)))
                            }
                            
                    
                            let avatarFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset - 52.0, y: floor((contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
                            transition.updateFrameAdditive(node: strongSelf.avatarNode, frame: avatarFrame)

                            if conferenceAvatars.count > 1 {
                                strongSelf.avatarNode.isHidden = true

                                let conferenceAvatarListContext: AnimatedAvatarSetContext
                                if let current = strongSelf.conferenceAvatarListContext {
                                    conferenceAvatarListContext = current
                                } else {
                                    conferenceAvatarListContext = AnimatedAvatarSetContext()
                                    strongSelf.conferenceAvatarListContext = conferenceAvatarListContext
                                }
                                let conferenceAvatarListNode: AnimatedAvatarSetNode
                                if let current = strongSelf.conferenceAvatarListNode {
                                    conferenceAvatarListNode = current
                                } else {
                                    conferenceAvatarListNode = AnimatedAvatarSetNode()
                                    strongSelf.conferenceAvatarListNode = conferenceAvatarListNode
                                    strongSelf.containerNode.addSubnode(conferenceAvatarListNode)
                                }
                                let avatarListContents = conferenceAvatarListContext.update(peers: Array(conferenceAvatars.prefix(3)), animated: false)
                                let avatarListSize = conferenceAvatarListNode.update(context: item.context, content: avatarListContents, itemSize: CGSize(width: CGFloat(multipleAvatarDiameter), height: CGFloat(multipleAvatarDiameter)), customSpacing: multipleAvatarDiameter - 8.0, font: multipleAvatarFont, animated: false, synchronousLoad: synchronousLoads)
                                conferenceAvatarListNode.frame = CGRect(origin: CGPoint(x: avatarFrame.minX + floor((avatarFrame.width - avatarListSize.width) / 2.0), y: avatarFrame.minY + floor((avatarFrame.height - avatarListSize.height) / 2.0)), size: avatarListSize)
                            } else {
                                strongSelf.avatarNode.isHidden = false
                                
                                strongSelf.conferenceAvatarListContext = nil
                                if let conferenceAvatarListNode = strongSelf.conferenceAvatarListNode {
                                    strongSelf.conferenceAvatarListNode = nil
                                    conferenceAvatarListNode.removeFromSupernode()
                                }
                            }
                            
                            let _ = titleApply()
                            let titleFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: verticalInset), size: titleLayout.size)
                            transition.updateFrameAdditive(node: strongSelf.titleNode, frame: titleFrame)
                            
                            let _ = statusApply()
                            transition.updateFrameAdditive(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
                            
                            if let currentCredibilityIconImage = currentCredibilityIconImage {
                                let iconNode: ASImageNode
                                if let current = strongSelf.credibilityIconNode {
                                    iconNode = current
                                } else {
                                    iconNode = ASImageNode()
                                    iconNode.isLayerBacked = true
                                    iconNode.displaysAsynchronously = false
                                    iconNode.displayWithoutProcessing = true
                                    strongSelf.credibilityIconNode = iconNode
                                }
                                iconNode.image = currentCredibilityIconImage
                                transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
                            } else if let credibilityIconNode = strongSelf.credibilityIconNode {
                                strongSelf.credibilityIconNode = nil
                                credibilityIconNode.removeFromSupernode()
                            }
                            
                            let _ = dateApply()
                            transition.updateFrameAdditive(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + params.width - dateRightInset - dateLayout.size.width, y: floor((nodeLayout.contentSize.height - dateLayout.size.height) / 2.0) + 2.0), size: dateLayout.size))
                            
                            if let outgoingIcon = outgoingIcon {
                                if strongSelf.typeIconNode.image !== outgoingIcon {
                                    strongSelf.typeIconNode.image = outgoingIcon
                                }
                                transition.updateFrameAdditive(node: strongSelf.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 79.0, y: floor((nodeLayout.contentSize.height - outgoingIcon.size.height) / 2.0)), size: outgoingIcon.size))
                            }
                            strongSelf.typeIconNode.isHidden = !hasOutgoing
                            
                            if let infoIcon = infoIcon {
                                if updatedInfoIcon {
                                    strongSelf.infoButtonNode.setImage(infoIcon, for: [])
                                }
                                transition.updateFrameAdditive(node: strongSelf.infoButtonNode, frame: CGRect(origin: CGPoint(x: revealOffset + params.width - infoIconRightInset - infoIcon.size.width, y: floor((nodeLayout.contentSize.height - infoIcon.size.height) / 2.0)), size: infoIcon.size))
                            }
                            transition.updateAlpha(node: strongSelf.infoButtonNode, alpha: item.editing ? 0.0 : 1.0)
                            
                            let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
                            strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
                            strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size)
                            strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
                            
                            strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
                            
                            strongSelf.accessibilityArea.accessibilityLabel = titleAttributedString?.string
                            strongSelf.accessibilityArea.accessibilityValue = statusAccessibilityString
                            strongSelf.accessibilityArea.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize)
                            
                            strongSelf.infoButtonNode.accessibilityLabel = item.presentationData.strings.Conversation_Info
                            
                            strongSelf.view.accessibilityCustomActions = [UIAccessibilityCustomAction(name: item.presentationData.strings.Common_Delete, target: strongSelf, selector: #selector(strongSelf.performLocalAccessibilityCustomAction(_:)))]
                            
                            strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
                            strongSelf.setRevealOptionsOpened(item.revealed, animated: animated)
                        }
                    })
                } else {
                    return (nil, { _ in })
                }
            })
        }
    }
    
    override func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
        let bounds = self.bounds
        accessoryItemNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -29.0), size: CGSize(width: bounds.size.width, height: 29.0))
    }
    
    override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
        self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
    }
    
    override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
        self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.3, removeOnCompletion: false)
    }
    
    override public func headers() -> [ListViewItemHeader]? {
        if let (item, _, _, _, _) = self.layoutParams {
            return item.header.flatMap { [$0] }
        } else {
            return nil
        }
    }
    
    @objc func infoPressed() {
        if let item = self.layoutParams?.0 {
            item.interaction.openInfo(item.topMessage.id.peerId, item.messages)
        }
    }
    
    override func revealOptionsInteractivelyOpened() {
        if let item = self.layoutParams?.0 {
            item.interaction.setMessageIdWithRevealedOptions(item.topMessage.id, nil)
        }
    }
    
    override func revealOptionsInteractivelyClosed() {
        if let item = self.layoutParams?.0 {
            item.interaction.setMessageIdWithRevealedOptions(nil, item.topMessage.id)
        }
    }
    
    override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
        super.updateRevealOffset(offset: offset, transition: transition)
        
        if let (item, params, _, _, _) = self.layoutParams {
            let revealOffset = offset
            
            let editingOffset: CGFloat
            if let editableControlNode = self.editableControlNode {
                editingOffset = editableControlNode.bounds.size.width
                var editableControlFrame = editableControlNode.frame
                editableControlFrame.origin.x = params.leftInset + offset
                transition.updateFrame(node: editableControlNode, frame: editableControlFrame)
            } else {
                editingOffset = 0.0
            }
            
            let leftInset: CGFloat = 86.0 + params.leftInset + editingOffset
            let rightInset: CGFloat = 13.0 + params.rightInset
            var infoIconRightInset: CGFloat = rightInset - 1.0
            
            var dateRightInset: CGFloat = 46.0 + params.rightInset
            if item.editing {
                dateRightInset += 5.0
                infoIconRightInset -= 36.0
            }
            
            var avatarFrame = self.avatarNode.frame
            avatarFrame.origin.x = revealOffset + leftInset - 52.0
            transition.updateFrameAdditive(node: self.avatarNode, frame: avatarFrame)
            
            if let conferenceAvatarListNode = self.conferenceAvatarListNode {
                var conferenceAvatarFrame = conferenceAvatarListNode.frame
                conferenceAvatarFrame.origin.x = avatarFrame.minX + floor((avatarFrame.width - conferenceAvatarFrame.width) / 2.0)
                transition.updateFrameAdditive(node: conferenceAvatarListNode, frame: conferenceAvatarFrame)
            }
            
            transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
            
            transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset, y: self.statusNode.frame.minY), size: self.statusNode.bounds.size))
            
            transition.updateFrameAdditive(node: self.dateNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + self.bounds.size.width - dateRightInset - self.dateNode.bounds.size.width, y: self.dateNode.frame.minY), size: self.dateNode.bounds.size))
            
            transition.updateFrameAdditive(node: self.typeIconNode, frame: CGRect(origin: CGPoint(x: revealOffset + leftInset - 81.0, y: self.typeIconNode.frame.minY), size: self.typeIconNode.bounds.size))
                        
            transition.updateFrameAdditive(node: self.infoButtonNode, frame: CGRect(origin: CGPoint(x: revealOffset + self.bounds.size.width - infoIconRightInset - self.infoButtonNode.bounds.width, y: self.infoButtonNode.frame.minY), size: self.infoButtonNode.bounds.size))
        }
    }

    override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
        self.setRevealOptionsOpened(false, animated: true)
        self.revealOptionsInteractivelyClosed()
        if let item = self.layoutParams?.0 {
            item.interaction.delete(item.messages.map { $0.id })
        }
    }
    
    @objc private func performLocalAccessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
        if let item = self.layoutParams?.0 {
            item.interaction.delete(item.messages.map { $0.id })
        }
    }
}
